
#ifndef THREAD_SAFE_QUEUE_H_
#define THREAD_SAFE_QUEUE_H_

#include <condition_variable>
#include <mutex>
#include <queue>

template <typename T>
class ThreadSafeQueue {
   public:
    /**
     * @brief ThreadSafeQueue constructor
     * @param [in] capacity: the queue capacity
     */
    ThreadSafeQueue(int capacity) {
        // check the input value: capacity is valid
        if (capacity >= kMinQueueCapacity && capacity <= kMaxQueueCapacity) {
            queue_capacity = capacity;
        } else {  // the input value: capacity is invalid, set the default value
            queue_capacity = kDefaultQueueCapacity;
        }
        _wait = 0;
        dataCount = 0;
    }

    /**
     * @brief ThreadSafeQueue constructor
     */
    ThreadSafeQueue() {
        queue_capacity = kDefaultQueueCapacity;
    }

    /**
     * @brief ThreadSafeQueue destructor
     */
    ~ThreadSafeQueue() = default;

    /**
     * @brief
     *
     */
    void Resize(int capacity) {
        if (capacity >= kMinQueueCapacity && capacity <= kMaxQueueCapacity) {
            queue_capacity = capacity;
        }
    }

    /**
     * @brief push data to queue
     * @param [in] input_value: the value will push to the queue
     * @return true: success to push data; false: fail to push data
     */
    bool Push(T input_value) {
        std::unique_lock<std::mutex> lock(m);

        push_cond.wait(lock, [this]() {
            return queue_.size() < queue_capacity;
        });

        // check current size is less than capacity
        queue_.push(input_value);
        pop_cond.notify_one();
        return true;
    }

    bool TryPush(T input_value) {
        std::unique_lock<std::mutex> lock(m);

        // check current size is less than capacity
        if (queue_.size() < queue_capacity) {
            queue_.push(input_value);
            pop_cond.notify_one();
            return true;
        } else {
            return false;
        }
    }

    /**
     * @brief pop data from queue
     * @return true: success to pop data; false: fail to pop data
     */
    T Pop() {
        std::unique_lock<std::mutex> lock(m);

        pop_cond.wait(lock, [this]() {
            return !queue_.empty();
        });

        T tmp_ptr = queue_.front();
        queue_.pop();
        push_cond.notify_one();
        return tmp_ptr;
    }

    bool TryPop(T& input_value) {
        std::unique_lock<std::mutex> lock(m);

        if (queue_.empty()) {  // check the queue is empty
            return false;
        } else {
            input_value = queue_.front();
            queue_.pop();
            push_cond.notify_one();
            return true;
        }
    }

    /**
     * @brief check the queue is empty
     * @return true: the queue is empty; false: the queue is not empty
     */
    bool Empty() {
        std::unique_lock<std::mutex> lock(m);
        return queue_.empty();
    }

    bool Full() {
        std::unique_lock<std::mutex> lock(m);
        return queue_.size() > queue_capacity;
    }

    size_t Capacity() {
        return queue_capacity;
    }

    /**
     * @brief get the queue size
     * @return the queue size
     */
    size_t Size() {
        std::unique_lock<std::mutex> lock(m);
        return queue_.size();
    }

    void Clear() {
        std::unique_lock<std::mutex> lock(m);
        std::queue<T> empty;
        std::swap(empty, queue_);
    }

   private:
    std::queue<T> queue_;  // the queue

    size_t queue_capacity;  // queue capacity

    mutable std::mutex mutex_;  // the mutex value

    const int kMinQueueCapacity = 1;  // the minimum queue capacity

    const int kMaxQueueCapacity = 10000;  // the maximum queue capacity

    const int kDefaultQueueCapacity = 10;  // default queue capacity

    int _wait;
    int dataCount;
    mutable std::mutex m;
    std::condition_variable pop_cond;
    std::condition_variable push_cond;
};

#endif /* THREAD_SAFE_QUEUE_H_ */
